package com.qiangesoft.blog.file.core.minio;

import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.date.DateUtil;
import com.qiangesoft.blog.common.constants.Constants;
import com.qiangesoft.blog.file.config.FileStoreConfig;
import com.qiangesoft.blog.file.enums.FileStoreTypeEnum;
import com.qiangesoft.blog.file.model.FileInfoVo;
import com.qiangesoft.blog.file.utils.MimeTypeUtils;
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Random;

/**
 * minio文件服务实现类
 *
 * @author lq
 * @date 2023-04-21
 */
@Component
@Slf4j
public class MinioTemplateImpl implements MinioTemplate {

    @Autowired
    private FileStoreConfig fileStoreConfig;

    @Autowired(required = false)
    private MinioClient minioClient;

    @Override
    public void createBucket(String bucketName) throws Exception {
        if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    @Override
    public FileInfoVo uploadFile(InputStream inputStream, String contentType) throws Exception {
        return uploadFile(inputStream, contentType, fileStoreConfig.getMinioConfig().getBucket());
    }

    @Override
    public FileInfoVo uploadFile(InputStream inputStream, String contentType, String bucketName) throws Exception {
        //判断文件是否为空
        if (null == inputStream) {
            return null;
        }

        // 判断存储桶是否存在，不存在则创建
        if (StringUtils.isBlank(bucketName)) {
            bucketName = fileStoreConfig.getMinioConfig().getBucket();
        }
        createBucket(bucketName);

        // 文件名
        String fileExtension = MimeTypeUtils.getExtension(contentType);
        String filePath = DateUtil.format(DateUtil.date(), "yyyy-MM-dd") + Constants.SLASH;
        String fileName = parseFileName(fileExtension);
        String objectName = filePath + fileName;
        int size = inputStream.available();

        minioClient.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(objectName)
                        .stream(inputStream, size, -1)
                        .contentType(contentType)
                        .build());
        String url = bucketName + Constants.SLASH + objectName;

        // 文件响应
        FileInfoVo fileInfoVo = new FileInfoVo();
        fileInfoVo.setOriginalFileName(fileName);
        fileInfoVo.setFileName(fileName);
        fileInfoVo.setStoreUrl(url);
        fileInfoVo.setCategory(bucketName);
        fileInfoVo.setFileExtension(fileExtension);
        fileInfoVo.setFileSize((long) size);
        fileInfoVo.setStoreType(FileStoreTypeEnum.FILE_MINIO.getCode());
        return fileInfoVo;
    }

    @Override
    public FileInfoVo uploadFile(MultipartFile file) throws Exception {
        return uploadFile(file, fileStoreConfig.getMinioConfig().getBucket());
    }

    @Override
    public FileInfoVo uploadFile(MultipartFile file, String bucketName) throws Exception {
        //判断文件是否为空
        if (null == file || 0 == file.getSize()) {
            return null;
        }

        //判断存储桶是否存在  不存在则创建
        if (StringUtils.isBlank(bucketName)) {
            bucketName = fileStoreConfig.getMinioConfig().getBucket();
        }
        createBucket(bucketName);

        //文件名
        String originalFilename = file.getOriginalFilename();

        //新的文件名 = 时间戳_随机数.后缀名
        assert originalFilename != null;
        String fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
        String filePath = DateUtil.format(DateUtil.date(), "yyyy-MM-dd") + Constants.SLASH;
        String fileName = parseFileName(fileExtension);
        String objectName = filePath + fileName;

        //开始上传
//        log.info("file压缩前大小:{}", file.getSize());
//        if (file.getSize() > maxSize) {
//            FileItemFactory fileItemFactory = new DiskFileItemFactory();
//            FileItem fileItem = fileItemFactory.createItem(fileName, "text/plain", true, fileName);
//            OutputStream outputStream = fileItem.getOutputStream();
//            Thumbnails.of(file.getInputStream()).scale(1f).outputFormat(originalFilename.substring(originalFilename.lastIndexOf(".") + 1)).outputQuality(0.25f).toOutputStream(outputStream);
//            file = new CommonsMultipartFile(fileItem);
//        }
//        log.info("file压缩后大小:{}", file.getSize());
        minioClient.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(objectName)
                        .stream(file.getInputStream(), file.getSize(), -1)
                        .contentType(file.getContentType())
                        .build());
        String url = bucketName + Constants.SLASH + objectName;

        // 文件响应
        FileInfoVo fileInfoVo = new FileInfoVo();
        fileInfoVo.setOriginalFileName(originalFilename);
        fileInfoVo.setFileName(fileName);
        fileInfoVo.setStoreUrl(url);
        fileInfoVo.setCategory(bucketName);
        fileInfoVo.setFileExtension(fileExtension);
        fileInfoVo.setFileSize(file.getSize());
        fileInfoVo.setStoreType(FileStoreTypeEnum.FILE_MINIO.getCode());
        return fileInfoVo;
    }

    @Override
    public List<Bucket> getAllBuckets() throws Exception {
        return minioClient.listBuckets();
    }

    @Override
    public Optional<Bucket> getBucket(String bucketName) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, InvalidResponseException, InternalException, ErrorResponseException, ServerException, XmlParserException, ServerException {
        return minioClient.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
    }

    @Override
    public void removeBucket(String bucketName) throws Exception {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    @Override
    public String getObjectURL(String bucketName, String objectName, Integer expires) throws Exception {
        return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(objectName).expiry(expires).build());
    }

    @Override
    public InputStream getObject(String bucketName, String objectName) throws Exception {
        return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    @Override
    public void putObject(String bucketName, String objectName, InputStream stream) throws
            Exception {
        minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(stream, stream.available(), -1).contentType(objectName.substring(objectName.lastIndexOf("."))).build());
    }

    @Override
    public void putObject(String bucketName, String objectName, InputStream stream, long
            size, String contextType) throws Exception {
        minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(stream, size, -1).contentType(contextType).build());
    }

    @Override
    public StatObjectResponse getObjectInfo(String bucketName, String objectName) throws Exception {
        return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    @Override
    public void removeObject(String bucketName, String objectName) throws Exception {
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    @Override
    public void removeObject(String objectName) throws Exception {
        minioClient.removeObjects(RemoveObjectsArgs.builder().bucket("").objects(null).build());
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(fileStoreConfig.getMinioConfig().getBucket()).object(objectName).build());
    }

    @Override
    public void removeObjects(List<String> objectNames) throws Exception {
        removeObjects(fileStoreConfig.getMinioConfig().getBucket(), objectNames);
    }

    @Override
    public void removeObjects(String bucketName, List<String> objectNames) throws Exception {
        List<DeleteObject> deleteObjectList = new ArrayList<>();
        for (String objectName : objectNames) {
            DeleteObject deleteObject = new DeleteObject(objectName);
            deleteObjectList.add(deleteObject);
        }
        minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(deleteObjectList).build());
    }

    @Override
    public FileInfoVo uploadVideo(MultipartFile file) throws Exception {
        return uploadVideo(file, fileStoreConfig.getMinioConfig().getBucket());
    }

    @Override
    public FileInfoVo uploadVideo(MultipartFile file, String bucketName) throws Exception {
        //判断文件是否为空
        if (null == file || 0 == file.getSize()) {
            return null;
        }

        //判断存储桶是否存在  不存在则创建
        if (StringUtils.isBlank(bucketName)) {
            bucketName = fileStoreConfig.getMinioConfig().getBucket();
        }
        createBucket(bucketName);

        //文件名
        String originalFilename = file.getOriginalFilename();
        //新的文件名 = 时间戳_随机数.后缀名
        assert originalFilename != null;
        String fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
        String filePath = DateUtil.format(DateUtil.date(), "yyyy-MM-dd") + Constants.SLASH;
        String fileName = parseFileName(fileExtension);
        String objectName = filePath + fileName;

        //开始上传
        log.info("file大小:{}", file.getSize());
        minioClient.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(objectName)
                        .stream(file.getInputStream(), file.getSize(), -1)
                        .contentType("video/mp4")
                        .build());
        String url = bucketName + Constants.SLASH + objectName;

        // 文件响应
        FileInfoVo fileInfoVo = new FileInfoVo();
        fileInfoVo.setOriginalFileName(originalFilename);
        fileInfoVo.setFileName(fileName);
        fileInfoVo.setStoreUrl(url);
        fileInfoVo.setCategory(bucketName);
        fileInfoVo.setFileExtension(fileExtension);
        fileInfoVo.setFileSize(file.getSize());
        return fileInfoVo;
    }

    @Override
    public void download(String filename, HttpServletRequest request, HttpServletResponse response) throws Exception {
        download(fileStoreConfig.getMinioConfig().getBucket(), filename, request, response);
    }

    @Override
    public void download(String bucketName, String filename, HttpServletRequest request, HttpServletResponse response) throws Exception {
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(filename).build();
        try (GetObjectResponse objectResponse = minioClient.getObject(objectArgs)) {
            byte[] buf = new byte[1024];
            int len;
            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
                while ((len = objectResponse.read(buf)) != -1) {
                    os.write(buf, 0, len);
                }
                os.flush();
                byte[] bytes = os.toByteArray();
                response.setCharacterEncoding("utf-8");
                response.setContentType("application/force-download");
                response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
                response.addHeader("Content-Disposition", "attachment;fileName=" + filenameEncoding(filename, request));
                try (ServletOutputStream stream = response.getOutputStream()) {
                    stream.write(bytes);
                    stream.flush();
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 构建文件名
     *
     * @return
     */
    private String parseFileName(String fileExtension) {
        long now = System.currentTimeMillis() / 1000;
        return now + "_" + new Random().nextInt(1000) + "." + fileExtension;
    }

    /**
     * 设置不同浏览器编码
     *
     * @param filename 文件名称
     * @param request  请求对象
     */
    private String filenameEncoding(String filename, HttpServletRequest request) throws UnsupportedEncodingException {
        // 获得请求头中的User-Agent
        String agent = request.getHeader("User-Agent");

        // 根据不同的客户端进行不同的编码
        if (agent.contains("MSIE")) {
            // IE浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        } else if (agent.contains("Firefox")) {
            // 火狐浏览器
            Base64Encoder base64Encoder = new Base64Encoder();
            filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
        } else {
            // 其它浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        }
        return filename;
    }
}